// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: jing

#pragma once

#include <string>

#include <rapidjson/document.h>
#include <rapidjson/rapidjson.h>

#include "coin/proto/coin_market_enums.pb.h"
#include "coin2/base/conversion.h"
#include "coin2/exchange/base/feed/update.h"
#include "coin2/exchange/base/feed_util/book_builder.h"
#include "coin2/exchange/base/feed_util/trade.h"
#include "coin2/exchange/base/feed_util/wallet.h"

namespace impl {
using coin2::exchange::base::market::ExchangeType;
using coin2::exchange::base::market::MarketType;

class BithumbFeedUpdateParser {
 public:
  static void
  ParseBook(BookBuilder* book_builder_, const rapidjson::Value& content_list, int64_t timestamp) {
    auto tx = book_builder_->NewTransaction(timestamp);

    for (const auto& ask_bid : content_list.GetArray()) {
      const std::string& order_type = ask_bid["orderType"].GetString();
      if (order_type == "1" || order_type == "ask") {
        tx.UpdateAsk(stod(ask_bid["price"].GetString()), stod(ask_bid["quantity"].GetString()));
      } else if (order_type == "2" || order_type == "bid") {
        tx.UpdateBid(stod(ask_bid["price"].GetString()), stod(ask_bid["quantity"].GetString()));
      } else {
        THROW() << "[Bithumb Feed Processor] unknown order type: " << order_type;
      }
    }
  }

  static void
  ParseSnapshot(BookBuilder* book_builder_, const rapidjson::Value& content_list, int64_t timestamp) {
    auto tx = book_builder_->NewTransaction(timestamp);
    tx.Clear();

    for (const auto& bids : content_list["bids"].GetArray()) {
      tx.UpdateBid(stod(bids["price"].GetString()), stod(bids["quantity"].GetString()));
    }

    for (const auto& asks : content_list["asks"].GetArray()) {
      tx.UpdateAsk(stod(asks["price"].GetString()), stod(asks["quantity"].GetString()));
    }
  }

  static void ParseTrade(const rapidjson::Value& transaction, Trade* trade, int64_t timestamp) {
    trade->price = stod(transaction["contPrice"].GetString());
    // "contQty":"0485.0798"
    trade->fill_qty = std::stod(transaction["contQty"].GetString());
    trade->side = ParseTradeSide(transaction);
    trade->timestamp = timestamp;
  }

  static coin2::exchange::feed::TradeSide ParseTradeSide(const rapidjson::Value& transaction) {
    if (transaction.HasMember("buySellGb")) {
      const std::string& side = transaction["buySellGb"].GetString();
      if (side == "1") {
        return coin2::exchange::feed::TRADE_SELL_SIDE;
      } else if (side == "2") {
        return coin2::exchange::feed::TRADE_BUY_SIDE;
      } else {
        return coin2::exchange::feed::TRADE_NO_SIDE;
      }
    } else if (transaction.HasMember("updn")) {
      const std::string& side = transaction["updn"].GetString();
      if (side == "ask") {
        return coin2::exchange::feed::TRADE_SELL_SIDE;
      } else if (side == "bid") {
        return coin2::exchange::feed::TRADE_BUY_SIDE;
      } else if (side == "dn") {
        return coin2::exchange::feed::TRADE_SELL_SIDE;
      } else if (side == "up") {
        return coin2::exchange::feed::TRADE_BUY_SIDE;
      } else {
        return coin2::exchange::feed::TRADE_NO_SIDE;
      }
    } else {
      return coin2::exchange::feed::TRADE_NO_SIDE;
    }
  }

  static fastfeed::proto::KlineInterval ParseKlineInterval(const std::string& period) {
    static std::map<std::string, fastfeed::proto::KlineInterval> interval_map = {
      {"1m", fastfeed::proto::KLINE_INTERVAL_1MIN},
      {"3m", fastfeed::proto::KLINE_INTERVAL_3MIN},
      {"5m", fastfeed::proto::KLINE_INTERVAL_5MIN},
      {"30m", fastfeed::proto::KLINE_INTERVAL_30MIN},
      {"1h", fastfeed::proto::KLINE_INTERVAL_1HOUR},
      {"6h", fastfeed::proto::KLINE_INTERVAL_6HOUR},
      {"12h", fastfeed::proto::KLINE_INTERVAL_12HOUR},
      {"24h", fastfeed::proto::KLINE_INTERVAL_1DAY}
    };
    auto iter = interval_map.find(period);
    if (iter == interval_map.end()) {
      
      return fastfeed::proto::KLINE_INTERVAL_INVALID;
    }

    return iter->second;
  }

  static bool Parse1Kline(
      const rapidjson::Value& kline_data,
      Klines* klines,
      int64_t timestamp,
      fastfeed::proto::KlineInterval interval) {
    CHECK_EQ(kline_data.Size(), 6);
    int64_t open_timestamp = stoll(kline_data[0].GetString()) * 1'000'000LL;
    auto result = klines->AddKline(open_timestamp);
    if (!result) return false;

    Kline* kline;
    bool freezed;
    std::tie(kline, freezed) = result.value();

    // stod don't support ".0003", use std::stod
    kline->open = std::stod(kline_data[1].GetString());
    kline->high = std::stod(kline_data[3].GetString());
    kline->low = std::stod(kline_data[4].GetString());
    kline->close = std::stod(kline_data[2].GetString());
    kline->volume = std::stod(kline_data[5].GetString());
    kline->timestamp = timestamp;
    kline->interval = interval;
    return freezed;
  }

  static void ParseWallet(const rapidjson::Value& transaction, Wallet* wallet) {
    wallet->can_deposit = transaction["deposit_status"] == "1" ? true : false;
    wallet->can_withdraw = transaction["withdrawal_status"] == "1" ? true : false;
  }
};
}  // namespace impl

using impl::BithumbFeedUpdateParser;
